"""
页管理器 - 负责页的分配、释放、读写操作
"""

import os
import sys
import struct
from typing import Dict, Set, Optional

# 添加当前目录到Python路径，支持直接运行
current_dir = os.path.dirname(os.path.abspath(__file__))
if current_dir not in sys.path:
    sys.path.insert(0, current_dir)

from constants import *
from statistics import StorageStats, Logger
from page_lock_manager import LockManager
from readahead_engine import ReadAheadEngine, AsyncReadAheadExecutor


class Page:
    """页对象"""
    
    def __init__(self, page_id: int, data: bytes = None):
        self.page_id = page_id
        self.data = data if data is not None else bytes(PAGE_SIZE)
        self.is_dirty = False  # 是否被修改
        self.is_pinned = False  # 是否被固定在内存中
        
        # 确保页大小正确
        if len(self.data) > PAGE_SIZE:
            self.data = self.data[:PAGE_SIZE]
        elif len(self.data) < PAGE_SIZE:
            self.data = self.data + bytes(PAGE_SIZE - len(self.data))
    
    def write_data(self, offset: int, data: bytes):
        """在页中写入数据"""
        if offset < 0 or offset >= PAGE_SIZE:
            raise ValueError(f"Invalid offset: {offset}")
        
        end_pos = min(offset + len(data), PAGE_SIZE)
        data_to_write = data[:end_pos - offset]
        
        # 转换为可变的bytearray进行修改
        page_data = bytearray(self.data)
        page_data[offset:offset + len(data_to_write)] = data_to_write
        self.data = bytes(page_data)
        self.is_dirty = True
    
    def read_data(self, offset: int, length: int) -> bytes:
        """从页中读取数据"""
        if offset < 0 or offset >= PAGE_SIZE:
            raise ValueError(f"Invalid offset: {offset}")
        
        end_pos = min(offset + length, PAGE_SIZE)
        return self.data[offset:end_pos]
    
    def clear(self):
        """清空页数据"""
        self.data = bytes(PAGE_SIZE)
        self.is_dirty = True
    
    def __str__(self) -> str:
        return f"Page(id={self.page_id}, dirty={self.is_dirty}, pinned={self.is_pinned})"


class PageManager:
    """页管理器 - 负责页的分配、释放、读写操作"""
    
    def __init__(self, db_file_path: str, enable_locking: bool = True, enable_readahead: bool = True):
        self.db_file_path = db_file_path
        self.free_pages: Set[int] = set()  # 空闲页集合
        self.allocated_pages: Set[int] = set()  # 已分配页集合
        self.next_page_id = 1  # 下一个可分配的页ID（0保留给元数据）
        self.enable_locking = enable_locking
        self.enable_readahead = enable_readahead
        
        # 添加线程同步锁
        import threading
        self._allocation_lock = threading.RLock()  # 页面分配锁
        self._metadata_lock = threading.RLock()    # 元数据操作锁
        self._file_io_lock = threading.RLock()     # 文件IO锁
        
        # 统计和日志
        self.stats = StorageStats()
        self.logger = Logger()
        
        # 锁管理器
        if self.enable_locking:
            self.lock_manager = LockManager()
            self.logger.info("INIT", 0, "Page manager initialized with locking enabled")
        else:
            self.lock_manager = None
            self.logger.info("INIT", 0, "Page manager initialized without locking")
        
        # 预读引擎
        if self.enable_readahead:
            self.readahead_engine = ReadAheadEngine(
                enable_readahead=True,
                window_size=4,
                max_readahead_pages=16
            )
            self.readahead_executor = AsyncReadAheadExecutor(self)
            self.logger.info("INIT", 0, "Page manager initialized with readahead enabled")
        else:
            self.readahead_engine = None
            self.readahead_executor = None
            self.logger.info("INIT", 0, "Page manager initialized without readahead")
        
        # 初始化数据库文件
        self._initialize_db_file()
        self._load_metadata()
    
    def _initialize_db_file(self):
        """初始化数据库文件"""
        if not os.path.exists(self.db_file_path):
            # 创建新文件并写入元数据页
            self.logger.info("INIT", 0, f"Creating new database file: {self.db_file_path}")
            with open(self.db_file_path, 'wb') as f:
                # 写入空的元数据页
                f.write(bytes(PAGE_SIZE))
            self._save_metadata()
        else:
            self.logger.info("INIT", 0, f"Opening existing database file: {self.db_file_path}")
    
    def _load_metadata(self):
        """从元数据页加载页管理信息"""
        try:
            with open(self.db_file_path, 'rb') as f:
                metadata_data = f.read(PAGE_SIZE)
                
            if len(metadata_data) < 16:
                # 新文件，初始化元数据
                self._save_metadata()
                return
            
            # 解析元数据（前16字节）
            next_page_id, allocated_count, free_count, total_pages = struct.unpack('IIII', metadata_data[:16])
            
            self.next_page_id = next_page_id
            self.stats.total_pages = total_pages
            self.stats.free_pages = free_count
            
            # 解析已分配页列表
            offset = 16
            self.allocated_pages.clear()
            for _ in range(allocated_count):
                if offset + 4 <= PAGE_SIZE:
                    page_id = struct.unpack('I', metadata_data[offset:offset+4])[0]
                    self.allocated_pages.add(page_id)
                    offset += 4
            
            # 解析空闲页列表
            for _ in range(free_count):
                if offset + 4 <= PAGE_SIZE:
                    page_id = struct.unpack('I', metadata_data[offset:offset+4])[0]
                    self.free_pages.add(page_id)
                    offset += 4
            
            self.logger.info("LOAD_META", 0, 
                           f"Loaded {allocated_count} allocated, {free_count} free pages")
            
        except Exception as e:
            self.logger.error("LOAD_META", 0, f"Failed to load metadata: {e}")
            # 如果加载失败，重新初始化
            self._save_metadata()
    
    def _save_metadata(self):
        """保存页管理信息到元数据页（线程安全）"""
        with self._metadata_lock:
            try:
                #元数据页缓冲区
                metadata = bytearray(PAGE_SIZE)
                
                # 保存基本信息（前16字节）
                struct.pack_into('IIII', metadata, 0,
                               self.next_page_id,
                               len(self.allocated_pages),
                               len(self.free_pages),
                               self.stats.total_pages)
                
                # 保存已分配页列表
                offset = 16
                for page_id in sorted(self.allocated_pages):
                    if offset + 4 <= PAGE_SIZE:
                        struct.pack_into('I', metadata, offset, page_id)
                        offset += 4
                
                # 保存空闲页列表
                for page_id in sorted(self.free_pages):
                    if offset + 4 <= PAGE_SIZE:
                        struct.pack_into('I', metadata, offset, page_id)
                        offset += 4
                
                # 写入文件（使用文件IO锁保护）
                with self._file_io_lock:
                    with open(self.db_file_path, 'r+b') as f:
                        f.seek(0)
                        f.write(metadata)
                        f.flush()
                
                self.logger.info("SAVE_META", 0, 
                               f"Saved {len(self.allocated_pages)} allocated, {len(self.free_pages)} free pages")
                
            except Exception as e:
                self.logger.error("SAVE_META", 0, f"Failed to save metadata: {e}")
    
    def allocate_page(self) -> int:
        """分配一个新页，返回页ID（线程安全）"""
        with self._allocation_lock:
            if self.free_pages:
                # 重用空闲页
                page_id = self.free_pages.pop()
                self.allocated_pages.add(page_id)
                self.logger.info("ALLOC", page_id, "Reused free page")
            else:
                # 分配新页
                page_id = self.next_page_id
                self.next_page_id += 1
                self.allocated_pages.add(page_id)
                self.stats.total_pages += 1
                self.logger.info("ALLOC", page_id, "Allocated new page")
            
            # 更新统计
            self.stats.record_allocation()
            
            # 确保文件大小足够
            self._ensure_file_size(page_id)
            
            # 保存元数据
            self._save_metadata()
            
            return page_id
    
    def deallocate_page(self, page_id: int) -> bool:
        """释放页（线程安全）"""
        with self._allocation_lock:
            if page_id == METADATA_PAGE_ID:
                self.logger.error("DEALLOC", page_id, "Cannot deallocate metadata page")
                return False
            
            if page_id not in self.allocated_pages:
                self.logger.warn("DEALLOC", page_id, "Page not allocated")
                return False
            
            # 移除分配标记，添加到空闲列表
            self.allocated_pages.remove(page_id)
            self.free_pages.add(page_id)
            
            # 清空页数据
            try:
                self._write_page_to_disk(page_id, bytes(PAGE_SIZE))
            except Exception as e:
                self.logger.error("DEALLOC", page_id, f"Failed to clear page: {e}")
            
            # 更新统计
            self.stats.record_deallocation()
            
            self.logger.info("DEALLOC", page_id, "Page deallocated")
            
            # 保存元数据
            self._save_metadata()
            
            return True
    
    def read_page(self, page_id: int, from_cache: bool = False) -> bytes:
        """从磁盘读取页数据，支持预读优化"""
        if page_id not in self.allocated_pages and page_id != METADATA_PAGE_ID:
            self.logger.error("READ", page_id, "Page not allocated")
            raise ValueError(f"Page {page_id} is not allocated")
        
        # 记录访问模式（用于预读）
        if self.enable_readahead and self.readahead_engine:
            self.readahead_engine.record_access(page_id, from_cache=from_cache)
        
        # 获取读锁
        if self.enable_locking and self.lock_manager:
            if not self.lock_manager.acquire_read_lock(page_id):
                self.logger.error("READ", page_id, "Failed to acquire read lock")
                raise RuntimeError(f"Failed to acquire read lock for page {page_id}")
        
        try:
            data = self._read_page_from_disk(page_id)
            self.stats.record_read()
            self.logger.info("READ", page_id, f"Read {len(data)} bytes")
            
            # 生成预读请求
            if self.enable_readahead and self.readahead_engine and self.readahead_executor:
                self._trigger_readahead(page_id)
            
            return data
        except Exception as e:
            self.logger.error("READ", page_id, f"Failed to read: {e}")
            raise
        finally:
            # 释放读锁
            if self.enable_locking and self.lock_manager:
                self.lock_manager.release_lock(page_id)
    
    def write_page(self, page_id: int, data: bytes) -> bool:
        """将页数据写入磁盘（线程安全）"""
        if page_id not in self.allocated_pages and page_id != METADATA_PAGE_ID:
            self.logger.error("WRITE", page_id, "Page not allocated")
            return False
        
        # 获取写锁
        if self.enable_locking and self.lock_manager:
            if not self.lock_manager.acquire_write_lock(page_id):
                self.logger.error("WRITE", page_id, "Failed to acquire write lock")
                return False
        
        if len(data) > PAGE_SIZE:
            self.logger.warn("WRITE", page_id, f"Data truncated from {len(data)} to {PAGE_SIZE} bytes")
            data = data[:PAGE_SIZE]
        elif len(data) < PAGE_SIZE:
            data = data + bytes(PAGE_SIZE - len(data))
        
        try:
            # 使用文件IO锁保护磁盘写操作
            with self._file_io_lock:
                self._write_page_to_disk(page_id, data)
                self.stats.record_write()
            self.logger.info("WRITE", page_id, f"Wrote {len(data)} bytes")
            return True
        except Exception as e:
            self.logger.error("WRITE", page_id, f"Failed to write: {e}")
            return False
        finally:
            # 释放写锁
            if self.enable_locking and self.lock_manager:
                self.lock_manager.release_lock(page_id)
    
    def _ensure_file_size(self, page_id: int):
        """确保文件大小足够容纳指定页"""
        required_size = (page_id + 1) * PAGE_SIZE
        try:
            current_size = os.path.getsize(self.db_file_path)
            if current_size < required_size:
                with open(self.db_file_path, 'r+b') as f:
                    f.seek(current_size)
                    f.write(bytes(required_size - current_size))
                    f.flush()
                self.logger.info("EXTEND", page_id, f"Extended file to {required_size} bytes")
        except Exception as e:
            self.logger.error("EXTEND", page_id, f"Failed to extend file: {e}")
            raise
    
    def _read_page_from_disk(self, page_id: int) -> bytes:
        """从磁盘读取页数据"""
        offset = page_id * PAGE_SIZE
        with open(self.db_file_path, 'rb') as f:
            f.seek(offset)
            data = f.read(PAGE_SIZE)
            if len(data) < PAGE_SIZE:
                data = data + bytes(PAGE_SIZE - len(data))
        
        self.stats.record_disk_read()
        return data
    
    def _write_page_to_disk(self, page_id: int, data: bytes):
        """将页数据写入磁盘"""
        offset = page_id * PAGE_SIZE
        with open(self.db_file_path, 'r+b') as f:
            f.seek(offset)
            f.write(data)
            f.flush()
        
        self.stats.record_disk_write()
    
    def get_allocated_pages(self) -> Set[int]:
        """获取已分配的页ID集合"""
        return self.allocated_pages.copy()
    
    def get_free_pages(self) -> Set[int]:
        """获取空闲页ID集合"""
        return self.free_pages.copy()
    
    def is_page_allocated(self, page_id: int) -> bool:
        """检查页是否已分配"""
        return page_id in self.allocated_pages
    
    def get_stats(self) -> StorageStats:
        """获取统计信息"""
        return self.stats
    
    def print_status(self):
        """打印页管理器状态"""
        print(f"\n=== 页管理器状态 ===")
        print(f"数据库文件: {self.db_file_path}")
        print(f"下一个页ID: {self.next_page_id}")
        print(f"已分配页数: {len(self.allocated_pages)}")
        print(f"空闲页数: {len(self.free_pages)}")
        print(f"总页数: {self.stats.total_pages}")
        print(f"统计信息: {self.stats}")
        if self.enable_readahead and self.readahead_engine:
            readahead_stats = self.readahead_engine.get_statistics()
            print(f"预读状态: {readahead_stats['enabled']}")
            print(f"预读模式: {readahead_stats['current_pattern']}")
            print(f"预读命中率: {readahead_stats['stats']['hit_rate']:.2%}")
        print("=" * 30)
    
    def _trigger_readahead(self, page_id: int):
        """触发预读操作"""
        try:
            # 获取预读请求
            readahead_requests = self.readahead_engine.get_readahead_requests(page_id)
            
            # 提交预读请求到异步执行器
            for request in readahead_requests:
                self.readahead_executor.submit_readahead_request(request)
                
        except Exception as e:
            self.logger.info("READAHEAD", page_id, f"Failed to trigger readahead: {e}")
    
    def get_readahead_statistics(self) -> dict:
        """获取预读统计信息"""
        if self.enable_readahead and self.readahead_engine:
            return self.readahead_engine.get_statistics()
        return {'enabled': False}
    
    def configure_readahead(self, **kwargs):
        """配置预读参数"""
        if self.enable_readahead and self.readahead_engine:
            self.readahead_engine.configure(**kwargs)
            self.logger.info("CONFIG", 0, f"Readahead configuration updated: {kwargs}")
    
    def release_all_locks(self) -> int:
        """释放当前线程持有的所有锁"""
        if self.enable_locking and self.lock_manager:
            released_count = self.lock_manager.release_all_locks()
            self.logger.info("RELEASE_ALL", 0, f"Released {released_count} locks")
            return released_count
        return 0
    
    def close(self):
        """关闭页管理器"""
        # 释放所有锁
        if self.enable_locking and self.lock_manager:
            self.release_all_locks()
        
        # 停止预读执行器
        if self.enable_readahead and self.readahead_executor:
            self.readahead_executor.stop()
        
        self._save_metadata()
        self.logger.info("CLOSE", 0, "Page manager closed")
